package xfuzzy.alg_genetico.modelo;

import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Stack;

import xfuzzy.alg_genetico.doble_cromosoma.finalversion.XfghlConfig;
import xfuzzy.lang.Operatorset;
import xfuzzy.lang.RuleSet;
import xfuzzy.lang.RuleSetCall;
import xfuzzy.lang.Specification;
import xfuzzy.lang.SystemModule;
import xfuzzy.lang.Type;
import xfuzzy.lang.Universe;
import xfuzzy.lang.Variable;
import xfuzzy.lang.XflException;
import xfuzzy.xfds.XfdsAttribute;
import xfuzzy.xfhl.model.XfhlConfig;
import xfuzzy.xfhl.model.hierStruct.XfhlHierStruct;
import xfuzzy.xfhl.model.hierStruct.XfhlModule;
import xfuzzy.xfhl.model.hierStruct.XfhlNode;
import xfuzzy.xfhl.model.spec.XfhlInputType;
import xfuzzy.xfhl.model.spec.XfhlOutputType;
import xfuzzy.xfhl.model.spec.XfhlRuleSet;

/**
 * Clase que dada una estructura HierStruct y una configuraci�n nos crea una
 * Specification
 * 
 */

public class MyXfhlSpecification {

	/**
	 * Metodo estatico que nos genera una Specificacition dada una
	 * XfhlHierStruct y una XfhlConfig
	 * 
	 * @param estrJerar
	 *            Estructura jerarquica que queremos convertir a Specification
	 * @param config
	 *            Configuracion para crear la especificacion
	 * @return Specification creada con la estructura y la configuracion deseada
	 * @throws XflException
	 */

	public static synchronized Specification create(XfhlHierStruct estrJerar, XfghlConfig config) throws XflException {

		// Contadores de variables que empiezan desde el valor maximo
		int numVarInnerIn = estrJerar.getNumModulos() * 2;
		int numVarInnerOut = estrJerar.getNumModulos();

		Specification specification = new Specification(config.getNameSystem());

		// Sistema jerarquico de salida
		SystemModule systemModule = new SystemModule();
		// Añadimos el sitema a la specification
		specification.setSystemModule(systemModule);

		// Conjunto de operadores
		Operatorset operSet = config.getOperSet();
		// Añadimos el operador al conjunto de operadores
		specification.addOperatorset(operSet);

		// Creamos los tipos de entrada
		XfdsAttribute[] attribute = config.getDataSet().getInputAttributes();
		// Lista de tipos de entrada
		java.util.List<Type> listInType = new LinkedList<Type>();
		// Lista de atributos de entrada
		java.util.List<XfdsAttribute> listAttributeIn = new LinkedList<XfdsAttribute>();

		for (int i = 0; i < attribute.length; i++) {
			listInType.add(XfhlInputType.create(config.getStileInput(), "T" + attribute[i].getName(),
					attribute[i].getUniverse(), config.getMfs()));
			listAttributeIn.add(attribute[i]);
		}

		/* Creamos el tipo de salida */
		XfdsAttribute attributeOut = config.getDataSet().getOutputAttributes()[0];
		Type outType = XfhlOutputType.create(config.getDefuzOperator(), "T" + attributeOut.getName(),
				attributeOut.getUniverse(), config.getMfs() * config.getMfs());

		// Cola para guardar los Modulos
		Queue<XfhlModule> colaModulos = new LinkedList<XfhlModule>();
		// Pila para guardar las variables del sistema
		Stack<Variable> pilaVarSystem = new Stack<Variable>();

		/* Encolamos el primer Modulo */
		colaModulos.add((XfhlModule) estrJerar.getModulo());

		/* Apilamos la variable de salida del sistema */
		Variable vOut = new Variable(attributeOut.getName(), outType, Variable.OUTPUT);
		pilaVarSystem.add(vOut);
		// Añadimos la variable de salida del sistema
		systemModule.addVariable(vOut);

		/* Lista de llamadas a las base de datos para tenerlas ordenadas */
		LinkedList<RuleSetCall> listRuleSetCall = new LinkedList<RuleSetCall>();
		/* Lista de tipos de la especificacion */
		LinkedList<Type> listTypes = new LinkedList<Type>();
		/* Lista de variables de entrada al sistema */
		Variable[] arrayVariable = new Variable[config.getnumVarEnt()];

		/* Añadimos el tipo de salida */
		listTypes.add(outType);

		// Tipo de las variables de entrada internas
		Type typeInnerIn = XfhlInputType.create(config.getStileInput(), "Name", new Universe(-1, 1), config.getMfs());
		Type typeInnerOut = XfhlOutputType.create(config.getDefuzOperator(), "Name", new Universe(-1, 1),
				config.getMfs() * config.getMfs());

		while (!colaModulos.isEmpty()) {

			// Variables de la llamada al ruleSet
			Variable ent1;
			Variable ent2;

			Variable sal = pilaVarSystem.pop();

			// Desencolamos un modulo
			XfhlModule mod = colaModulos.remove();

			// Si la entrada 2 es un modulo
			if (((XfhlModule) (mod)).getEnt2() instanceof XfhlModule) {
				// Añadimos una variable interna al sistema
				ent2 = systemModule.addInnerVariable();
				pilaVarSystem.add(ent2);
				colaModulos.add(((XfhlModule) ((XfhlModule) (mod)).getEnt2()));
			} else {
				// Si la entrada 2 es un nodo es una entrada
				// Sabemos que variable de entrada es con el valor del nodo
				int numEntrada = ((XfhlNode) ((XfhlModule) (mod)).getEnt2()).getValor();
				// Accdemos los datos de la variable de entrada
				Type typeIn = (Type) listInType.get(numEntrada);
				XfdsAttribute attributeIn = listAttributeIn.get(numEntrada);

				typeIn.setName("T" + attributeIn.getName());

				// Nos creamos la variable de entrada
				ent2 = new Variable(attributeIn.getName(), typeIn, Variable.INPUT);

				/* Añadimos el tipo y la variable de entrada */
				listTypes.add(typeIn);
				arrayVariable[numEntrada] = ent2;
			}

			// Si la entrada 1 es un modulo
			if (((XfhlModule) (mod)).getEnt1() instanceof XfhlModule) {
				// Añadimos una variable interna al sistema
				ent1 = systemModule.addInnerVariable();
				pilaVarSystem.add(ent1);
				colaModulos.add(((XfhlModule) ((XfhlModule) (mod)).getEnt1()));
			} else {
				// Sabemos que variable de entrada es con el valor del nodo
				int numEntrada = ((XfhlNode) ((XfhlModule) (mod)).getEnt1()).getValor();
				// Accdemos los datos de la variable de entrada
				Type typeIn = (Type) listInType.get(numEntrada);
				XfdsAttribute attributeIn = listAttributeIn.get(numEntrada);

				typeIn.setName("T" + attributeIn.getName());

				ent1 = new Variable(attributeIn.getName(), typeIn, Variable.INPUT);

				/* Añadimos el tipo y la variable de entrada */
				listTypes.add(typeIn);
				arrayVariable[numEntrada] = ent1;
			}

			/* Creamos un nuevo RuleSet */
			// Variable 1
			Type i1T;
			if (ent1.getType() == null) {
				i1T = (Type) (typeInnerIn.clone());
				i1T.setName("TI_" + numVarInnerIn);
				// A�adimos el tipo de la variable 1
				listTypes.add(i1T);
			} else { // Variable de entrada del sistema
				i1T = ((Type) ent1.getType());
			}
			Variable i1 = new Variable("innI_" + numVarInnerIn, i1T, Variable.INPUT);
			numVarInnerIn--;

			// Variable 2
			Type i2T;
			if (ent2.getType() == null) {
				i2T = (Type) (typeInnerIn.clone());
				i2T.setName("TI_" + numVarInnerIn);
				// Añadimos el tipo de la variable 2
				listTypes.add(i2T);

			} else { // Variable de entrada del sistema
				i2T = ((Type) ent2.getType());
			}
			Variable i2 = new Variable("innI_" + numVarInnerIn, i2T, Variable.INPUT);
			numVarInnerIn--;

			// Variable salida
			Type oT;
			if (sal.getType() == null) {
				oT = (Type) typeInnerOut.clone();
				oT.setName("TO_" + numVarInnerOut);
				// Añadimos el tipo de la variable de salida
				listTypes.add(oT);
			} else { // La variable de salida del sistema
				oT = ((Type) sal.getType());
			}
			Variable out = new Variable("innO_" + numVarInnerOut, oT, Variable.OUTPUT);
			numVarInnerOut--;

			// Nos creamos el RuleSet
			RuleSet ruleSet = XfhlRuleSet.create("KB_" + mod.getIdMod(), i1, i2, out, operSet);
			// Le asignamos la base de conocimiento a la variable de salida
			out.setKnowledgeBase(ruleSet);
			i1.setKnowledgeBase(ruleSet);
			i2.setKnowledgeBase(ruleSet);

			Variable[] inputs = { ent1, ent2 };
			Variable[] outputs = { sal };

			listRuleSetCall.add(0, new RuleSetCall(ruleSet, inputs, outputs));
		}

		/* Agregamos los tipos a la especificacion */
		for (Type type : listTypes) {
			specification.addType(type);
		}

		/* Agregamos todos los RuleSetCall y los RuleSet a la especificacion */
		for (RuleSetCall ruleSetCall : listRuleSetCall) {
			specification.addRulebase(ruleSetCall.getRulebase());
			systemModule.addCall(ruleSetCall.getRulebase(), ruleSetCall.getInputVariables(),
					ruleSetCall.getOutputVariables());
		}

		/* Agregamos las variables de entrada de al sistema */
		for (Variable variable : arrayVariable) {
			systemModule.addVariable(variable);
		}

		return specification;
	}
	
	public static synchronized Specification create(XfhlHierStruct estrJerar, XfhlConfig config, List<XfhlModule> modulos) throws XflException {

		// Contadores de variables que empiezan desde el valor maximo
		int numVarInnerIn = estrJerar.getNumModulos() * 2;
		int numVarInnerOut = estrJerar.getNumModulos();

		Specification specification = new Specification(config.getNameSystem());

		// Sistema jerarquico de salida
		SystemModule systemModule = new SystemModule();
		// Añadimos el sitema a la specification
		specification.setSystemModule(systemModule);

		// Conjunto de operadores
		Operatorset operSet = config.getOperSet();
		// Añadimos el operador al conjunto de operadores
		specification.addOperatorset(operSet);

		// Creamos los tipos de entrada
		XfdsAttribute[] attribute = config.getDataSet().getInputAttributes();
		// Lista de tipos de entrada
		java.util.List<Type> listInType = new LinkedList<Type>();
		// Lista de atributos de entrada
		java.util.List<XfdsAttribute> listAttributeIn = new LinkedList<XfdsAttribute>();

		for (int i = 0; i < attribute.length; i++) {
			listInType.add(XfhlInputType.create(config.getStileInput(), "T" + attribute[i].getName(),
					attribute[i].getUniverse(), config.getMfs()));
			listAttributeIn.add(attribute[i]);
		}

		/* Creamos el tipo de salida */
		XfdsAttribute attributeOut = config.getDataSet().getOutputAttributes()[0];
		Type outType = XfhlOutputType.create(config.getDefuzOperator(), "T" + attributeOut.getName(),
				attributeOut.getUniverse(), config.getMfs() * config.getMfs());

		// Cola para guardar los Modulos
		Queue<XfhlModule> colaModulos = new LinkedList<XfhlModule>();
		// Pila para guardar las variables del sistema
		Stack<Variable> pilaVarSystem = new Stack<Variable>();

		/* Encolamos el primer Modulo */
		colaModulos.add((XfhlModule) estrJerar.getModulo());

		/* Apilamos la variable de salida del sistema */
		Variable vOut = new Variable(attributeOut.getName(), outType, Variable.OUTPUT);
		pilaVarSystem.add(vOut);
		// Añadimos la variable de salida del sistema
		systemModule.addVariable(vOut);

		/* Lista de llamadas a las base de datos para tenerlas ordenadas */
		LinkedList<RuleSetCall> listRuleSetCall = new LinkedList<RuleSetCall>();
		/* Lista de tipos de la especificacion */
		LinkedList<Type> listTypes = new LinkedList<Type>();
		/* Lista de variables de entrada al sistema */
		Variable[] arrayVariable = new Variable[config.getnumVarEnt()];

		/* Añadimos el tipo de salida */
		listTypes.add(outType);

		// Tipo de las variables de entrada internas
		Type typeInnerIn = XfhlInputType.create(config.getStileInput(), "Name", new Universe(-1, 1), config.getMfs());
		Type typeInnerOut = XfhlOutputType.create(config.getDefuzOperator(), "Name", new Universe(-1, 1),
				config.getMfs() * config.getMfs());

		while (!colaModulos.isEmpty()) {

			// Variables de la llamada al ruleSet
			Variable ent1;
			Variable ent2;

			Variable sal = pilaVarSystem.pop();

			// Desencolamos un modulo
			XfhlModule mod = colaModulos.remove();

			// Si la entrada 2 es un modulo
			if (((XfhlModule) (mod)).getEnt2() instanceof XfhlModule) {
				// Añadimos una variable interna al sistema
				ent2 = systemModule.addInnerVariable();
				pilaVarSystem.add(ent2);
				colaModulos.add(((XfhlModule) ((XfhlModule) (mod)).getEnt2()));
			} 
			else {
				// Si la entrada 2 es un nodo es una entrada
				// Sabemos que variable de entrada es con el valor del nodo
				int numEntrada = ((XfhlNode) ((XfhlModule) (mod)).getEnt2()).getValor();
				// Accdemos los datos de la variable de entrada
				Type typeIn = (Type) listInType.get(numEntrada);
				XfdsAttribute attributeIn = listAttributeIn.get(numEntrada);

				typeIn.setName("T" + attributeIn.getName());

				// Nos creamos la variable de entrada
				ent2 = new Variable(attributeIn.getName(), typeIn, Variable.INPUT);

				/* Añadimos el tipo y la variable de entrada */
				listTypes.add(typeIn);
				arrayVariable[numEntrada] = ent2;
			}

			// Si la entrada 1 es un modulo
			if (((XfhlModule) (mod)).getEnt1() instanceof XfhlModule) {
				// A�adimos una variable interna al sistema
				ent1 = systemModule.addInnerVariable();
				pilaVarSystem.add(ent1);
				colaModulos.add(((XfhlModule) ((XfhlModule) (mod)).getEnt1()));
			} 
			else {
				// Sabemos que variable de entrada es con el valor del nodo
				int numEntrada = ((XfhlNode) ((XfhlModule) (mod)).getEnt1()).getValor();
				// Accdemos los datos de la variable de entrada
				Type typeIn = (Type) listInType.get(numEntrada);
				XfdsAttribute attributeIn = listAttributeIn.get(numEntrada);

				typeIn.setName("T" + attributeIn.getName());

				ent1 = new Variable(attributeIn.getName(), typeIn, Variable.INPUT);

				/* Añadimos el tipo y la variable de entrada */
				listTypes.add(typeIn);
				arrayVariable[numEntrada] = ent1;
			}

			/* Creamos un nuevo RuleSet */
			// Variable 1
			Type i1T;
			if (ent1.getType() == null) {
				i1T = (Type) (typeInnerIn.clone());
				i1T.setName("TI_" + numVarInnerIn);
				// Añadimos el tipo de la variable 1
				listTypes.add(i1T);
			} 
			else { // Variable de entrada del sistema
				i1T = ((Type) ent1.getType());
			}
			Variable i1 = new Variable("innI_" + numVarInnerIn, i1T, Variable.INPUT);
			numVarInnerIn--;

			// Variable 2
			Type i2T;
			if (ent2.getType() == null) {
				i2T = (Type) (typeInnerIn.clone());
				i2T.setName("TI_" + numVarInnerIn);
				// Añadimos el tipo de la variable 2
				listTypes.add(i2T);

			} 
			else { // Variable de entrada del sistema
				i2T = ((Type) ent2.getType());
			}
			Variable i2 = new Variable("innI_" + numVarInnerIn, i2T, Variable.INPUT);
			numVarInnerIn--;

			// Variable salida
			Type oT;
			if (sal.getType() == null) {
				oT = (Type) typeInnerOut.clone();
				oT.setName("TO_" + numVarInnerOut);
				// Añadimos el tipo de la variable de salida
				listTypes.add(oT);
			} 
			else { // La variable de salida del sistema
				oT = ((Type) sal.getType());
			}
			Variable out = new Variable("innO_" + numVarInnerOut, oT, Variable.OUTPUT);
			numVarInnerOut--;

			// Nos creamos el RuleSet
			RuleSet ruleSet = XfhlRuleSet.create("KB_" + mod.getIdMod(), i1, i2, out, operSet);
			// Le asignamos la base de conocimiento a la variable de salida
			out.setKnowledgeBase(ruleSet);
			i1.setKnowledgeBase(ruleSet);
			i2.setKnowledgeBase(ruleSet);

			Variable[] inputs = { ent1, ent2 };
			Variable[] outputs = { sal };

			listRuleSetCall.add(0, new RuleSetCall(ruleSet, inputs, outputs));
		}

		/* Agregamos los tipos a la especificacion */
		for (Type type : listTypes) {
			specification.addType(type);
		}

		/* Agregamos todos los RuleSetCall y los RuleSet a la especificacion */
		for (RuleSetCall ruleSetCall : listRuleSetCall) {
			specification.addRulebase(ruleSetCall.getRulebase());
			systemModule.addCall(ruleSetCall.getRulebase(), ruleSetCall.getInputVariables(),
					ruleSetCall.getOutputVariables());
		}

		/* Agregamos las variables de entrada de al sistema */
		for (Variable variable : arrayVariable) {
			systemModule.addVariable(variable);
		}
		
		// System.out.println(specification.toXfl());
		modulos.add((XfhlModule)estrJerar.getModulo());
		return specification;
	}
}
